﻿<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Consuming ASP.net WebServices, WCF Services and static Page methods from JavaScript
        (sans MS AJAX)</title>
    <link href="css/demo.css" rel="stylesheet" type="text/css" />

    <script src="js/salient/accessControl.js" type="text/javascript"></script>

    <script type="text/javascript">

        // 
        // Consuming ASP.net WebServices, WCF Services and static Page methods from JavaScript (sans MS AJAX) Sample Code
        // Author: Sky Sanders (sky.sanders@gmail.com)
        //

        // IE and 1203X errors

        // to make development and testing in IE with a VS web app project and dev server (which uses ports)
        // i had to implement a lot of kludges to handle irratic and infamous 1203x errors that occur in IE
        // when an ajax request uses a port or ssl. Apparently this has to do with the shitty HTTP1.1 implementation
        // that is and has been used in IE since v6.
        // These issues are NOT present in ANY other browser, and it is my experience that when running in IIS without a custom port
        // none of these kludges are necessary.

        // in any case - if a stray 1203x error does sneak past it is not the code, it is the browser.

        var q;
        var directory = "";
        var header;
        var headername = "x-access-control";

        var _userName = "member";
        var _userPass = "member";
        var _adminName = "admin";
        var _adminPass = "admin";
        var expected = { anon: 200, users: 200, admin: 200, current: null };
        var testResults = { passed: 0, failed: 0 };
        window.onload = function() {

            function enqueueInnerBatch() {
                q.enqueue(javaScriptFragment);
                q.enqueue(htmlFragment);
                q.enqueue(webFormGet);
                q.enqueue(webFormPost);
                q.enqueue(pageMethodPostSimple);
                q.enqueue(pageMethodPostComplex);
                q.enqueue(httpHandlerGetSimple);
                q.enqueue(httpHandlerPostSimple);
                q.enqueue(httpHandlerPostJSONComplex);
                q.enqueue(xmlWebServiceGet);
                q.enqueue(xmlWebServicePost);
                q.enqueue(scriptServicePostSimple);
                q.enqueue(scriptServicePostComplex);
                q.enqueue(wcfSimple);
                q.enqueue(wcfComplex);
            }

            function enqueueBatch() {
                q.enqueue(setDirectoryPublic);
                enqueueInnerBatch();
                q.enqueue(setDirectoryUsers);
                enqueueInnerBatch();
                q.enqueue(setDirectoryAdmin);
                enqueueInnerBatch();
            }

            q = new asyncBufferedQueue();

            q.enqueue(setAuthHeaderAnonymous);
            enqueueBatch();
            q.enqueue(setAuthHeaderUser);
            enqueueBatch();
            q.enqueue(setAuthHeaderAdmin);
            enqueueBatch();

            // kick it off
            q.dequeue();
        }
        function setAuthHeaderAnonymous() {
            printHeader("anonymous");
            header = new salient.accessControl.AccessControlHeader({ "mode": "script", "logout": "true" });
            expected.admin = 403;
            expected.users = 401;
            expected.anon = 200;
            q.dequeue();
        }

        function setAuthHeaderUser() {
            printHeader("Logged in as 'user'");
            header = new salient.accessControl.AccessControlHeader({ "mode": "script", "username": _userName, "password": _userPass, "logout": "true" });
            expected.admin = 403;
            expected.users = 200;
            expected.anon = 200;

            q.dequeue();
        }
        function setAuthHeaderAdmin() {
            printHeader("Logged in as 'admin'");
            header = new salient.accessControl.AccessControlHeader({ "mode": "script", "username": _adminName, "password": _adminPass, "logout": "true" });
            expected.admin = 200;
            expected.users = 200;
            expected.anon = 200;

            q.dequeue();
        }

        function setDirectoryPublic() {
            printHeader("accessing public");
            // properly formatted relative paths helps eliminate IE network errors
            // i.e. './'
            directory = "./public";
            expected.current = "anon";
            q.dequeue();
        }
        function setDirectoryUsers() {
            printHeader("accessing users");
            directory = "./users";
            expected.current = "users";
            q.dequeue();
        }
        function setDirectoryAdmin() {
            printHeader("accessing admin");
            directory = "./admin";
            expected.current = "admin";
            q.dequeue();
        }

        // create a simple async queue to keep our results
        // in an appreciable order and to allow a bit of breathing
        // room in between to placate IE
        var asyncBufferedQueue = function() {
            var innerList = [];

            this.enqueue = function(fn) {
                innerList.push(fn);
            }

            function dequeueCore() {
                if (innerList.length > 0) {
                    var fn = innerList.shift();
                    fn();
                }
            }

            this.dequeue = function() {
                dequeueCore();
                // small buffer helps eliminate IE network errors
                //window.setTimeout(dequeueCore, 1);
            }
        };

        function printHeader(text) {
            var block = document.body.appendChild(document.createElement("div"));
            block.appendChild(document.createTextNode(text));

        }
        function processResponse(title, xhr) {
            /// <summary>
            /// Dump out an XHR response text to page
            /// </summary>
            /// <param name="title" type="String">value to head the blockquoted pre</param>
            /// <param name="xhr" type="XMLHttpRequest"></param>

            title = directory + " " + title;

            title += " " + xhr.status + " " + xhr.statusText;
            content = xhr.responseText;

            if (xhr.status === 12030 || xhr.status === 12031) {
                var stop = "";
            }



            var block = document.body.appendChild(document.createElement("blockquote"));
            block.appendChild(document.createTextNode(title));
            if (expected[expected.current] != xhr.status) {
                block.className = "error";
                testResults.failed++;
            }
            else {
                testResults.passed++;
            }
            document.getElementById("testResults").innerHTML = "Passed:" + testResults.passed + " Failed:" + testResults.failed;
            var pre = block.appendChild(document.createElement("pre"));
            pre.appendChild(document.createTextNode(content));

            xhr.abort();
            xhr = null;
            // run the next async block
            q.dequeue();
        }

        function executeXhr(method, url, contentType, postData, retryCount) {

            /// <summary>
            /// Execute an ajax request with retries for farking IE errors.
            /// REAL errors are passed to processResponse as it is
            /// errors that we are most interested in.
            /// </summary>
            /// <param name="method" type="String"></param>
            /// <param name="url" type="String"></param>
            /// <param name="contentType" type="String"></param>
            /// <param name="postData" type="String"></param>
            /// <param name="retryCount" type="Number" integer="true">internal use</param>



            postData = postData || null;

            retryCount = retryCount || 0;

            var xhr = createXHR();

            xhr.open(method, url, true);

            function complete() {
                processResponse(method + ":" + url, xhr);
            }

            function retry() {
                // necessary for catching irratic IE errors related to
                // specifying ports and/or ssl
                executeXhr(method, url, contentType, postData, retryCount);
            }

            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    switch (xhr.status) {
                        case 0: // opera throws this one ebby 1ce in a while
                        case 12029:
                        case 12030:
                        case 12031:
                        case 12152:
                        case 12159:
                            // necessary for catching irratic IE errors related to 
                            // specifying ports and/or ssl  
                            retryCount += 1;
                            if (retryCount < 5) {
                                retry();
                                //window.setTimeout(retry, 5);
                                return;
                            }
                    }
                    complete();
                    xhr.abort;
                }
            }

            xhr.setRequestHeader(headername, header.toJSON());
            if (method.toLowerCase() === "post") {
                xhr.setRequestHeader("content-type", contentType);
                // setting the content length explicitely apparently helps eliminate IE network errors
                xhr.setRequestHeader("content-length", postData.length);
            }
            xhr.send(postData);
        }

        function createXHR() {
            // a memoizing XMLHttpRequest factory.
            var xhr;
            var factories = [
                    function() { return new XMLHttpRequest(); },
                    function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
                    function() { return new ActiveXObject("Msxml3.XMLHTTP"); },
                    function() { return new ActiveXObject("Microsoft.XMLHTTP"); } ];
            for (var i = 0; i < factories.length; i++) {
                try {
                    xhr = factories[i]();
                    // memoize the factory so we don't have to look for it again.
                    this.createXHR = factories[i];
                    return xhr;
                } catch (e) { }
            }
        }

        function javaScriptFragment() {
            executeXhr("GET", directory + "/JavaScriptFragment.js");
        }
        function htmlFragment() {
            executeXhr("GET", directory + "/HtmlFragment.txt");
        }
        function webFormGet() {
            executeXhr("GET", directory + "/Default.aspx?name=foo");
        }
        function webFormPost() {
            executeXhr("POST", directory + "/Default.aspx", "application/x-www-form-urlencoded", "name=foo");
        }
        function pageMethodPostSimple() {
            executeXhr("POST", directory + "/Default.aspx/Echo", "application/json", '{"input": "Hello World!"}');
        }
        function pageMethodPostComplex() {
            executeXhr("POST", directory + "/Default.aspx/EchoTestObject", "application/json", '{input: { Name: "Foo", Age: 21 }}');
        }

        function httpHandlerGetSimple() {
            executeXhr("GET", directory + "/HTTPHandler.ashx?name=foo");
        }
        function httpHandlerPostSimple() {
            executeXhr("POST", directory + "/HTTPHandler.ashx", "application/x-www-form-urlencoded", "name=foo");
        }
        function httpHandlerPostJSONComplex() {
            executeXhr("POST", directory + "/HTTPHandler.ashx", "application/json", '{input: { Name: "Foo", Age: 21 }}');
        }
        function xmlWebServiceGet() {
            executeXhr("GET", directory + "/WebService1.asmx/Echo?input=Hello%20World!");
        }
        function xmlWebServicePost() {
            executeXhr("POST", directory + "/WebService1.asmx/Echo", "application/x-www-form-urlencoded", "input=Hello%20World!");
        }
        function scriptServicePostSimple() {
            executeXhr("POST", directory + "/WebService1.asmx/Echo", "application/json", '{"input": "Hello World!"}');
        }
        function scriptServicePostComplex() {
            executeXhr("POST", directory + "/WebService1.asmx/EchoTestObject", "application/json", '{input: { Name: "Foo", Age: 21 }}');
        }
        function wcfSimple() {
            executeXhr("POST", directory + "/Service1.svc/Echo", "application/json", '{"input": "Hello World!"}');
        }
        function wcfComplex() {
            executeXhr("POST", directory + "/Service1.svc/EchoTestObject", "application/json", '{"input": { "Name": "Foo", "Age": 21 }}');
        }
    
    </script>

</head>
<body>
    <h2>
        Consuming ASP.net WebServices, WCF Services and static Page methods from JavaScript
        (sans MS AJAX)</h2>
    <a href="Default.aspx">Default.aspx</a>
    <h3>
        Sample code output:</h3>
        <div id="testResults"></div>
</body>
</html>
